<script lang="tsx" setup>
/**
 * 验证码组件
 * 修复了使用rem单位时Canvas绘制问题
 */
import {computed, nextTick, onMounted, ref, watch} from "vue"
import {processRequest} from "@/framework";

/**
 * 定义props
 */
const props = withDefaults(defineProps<{
  /**
   * 服务器验证码地址（如果使用后端验证码）
   */
  url?: string,
  /**
   * 最小随机数
   */
  minValue?: number,
  /**
   * 最大随机数
   */
  maxValue?: number,
  /**
   * 最小字体大小
   */
  minFontSize?: number,
  /**
   * 最大字体大小
   */
  maxFontSize?: number,
  /**
   * 最小背景色
   */
  minBackgroundColor?: number,
  /**
   * 最大背景色
   */
  maxBackgroundColor?: number,
  /**
   * 最小线条色
   */
  minLineColor?: number,
  /**
   * 最大线条色
   */
  maxLineColor?: number,
  /**
   * 最小点颜色
   */
  minDotColor?: number;
  /**
   * 最大点颜色
   */
  maxDotColor?: number,
  /**
   * 最小倾斜角
   */
  minDeg?: number,
  /**
   * 最大倾斜角
   */
  maxDeg?: number,
  /**
   * 宽度
   */
  width?: number,
  /**
   * 高度
   */
  height?: number,
  /**
   * 是否开启rem
   */
  isRem?: boolean,
  /**
   * 验证码长度
   */
  length?: number
}>(), {
  minValue: 1000,
  maxValue: 9999,
  minFontSize: 25,
  maxFontSize: 35,
  minLineColor: 100,
  maxLineColor: 200,
  minBackgroundColor: 100,
  maxBackgroundColor: 200,
  minDotColor: 60,
  maxDotColor: 120,
  minDeg: -30,
  maxDeg: 30,
  width: 90,
  height: 38,
  isRem: true,
  length: 4
})

/**
 * 值
 */
const value = ref("")

/**
 * guid
 */
const guid = ref("")

/**
 * 设备像素比
 */
const dpr = ref(1)

/**
 * 初始化
 */
onMounted(() => {
  guid.value = "id_" + Math.random() + Math.random()
  dpr.value = window.devicePixelRatio || 1
  handleRefresh()
})

/**
 * 刷新验证码
 */
const handleRefresh = async () => {
  if (props.url) {
    value.value = await processRequest.get(props.url)
    value.value = randomNum(props.minValue, props.maxValue).toString()
  } else {
    value.value = generateCode(props.length)
  }
  await nextTick(() => {
    drawCanvas()
  })
}

/**
 * 生成随机验证码
 */
const generateCode = (length: number) => {
  const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz12345678'
  let result = ''
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length))
  }
  return result
}

/**
 * 获取随机数
 */
const randomNum = (min: number, max: number) => {
  return Math.floor(Math.random() * (max - min) + min)
}

/**
 * 获取随机颜色
 */
const randomColor = (min: number, max: number) => {
  const r = randomNum(min, max)
  const g = randomNum(min, max)
  const b = randomNum(min, max)
  return "rgb(" + r + "," + g + "," + b + ")"
}

/**
 * 绘图初始化
 */
const drawCanvas = () => {
  const canvas = document.getElementById(guid.value) as HTMLCanvasElement
  if (!canvas) return

  const ctx = canvas.getContext("2d")
  if (!ctx) return

  // 获取实际显示尺寸（可能已转换为像素）
  let displayWidth = props.width
  let displayHeight = props.height

  // 如果使用rem，需要将rem转换为实际像素值
  if (props.isRem) {
    const htmlFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)
    displayWidth = props.width * htmlFontSize
    displayHeight = props.height * htmlFontSize
  }

  // 设置Canvas实际绘制尺寸（考虑设备像素比）
  canvas.width = displayWidth * dpr.value
  canvas.height = displayHeight * dpr.value

  // 缩放上下文以适配高DPI设备
  ctx.scale(dpr.value, dpr.value)

  // 清除Canvas
  ctx.clearRect(0, 0, displayWidth, displayHeight)

  // 设置文本基线
  ctx.textBaseline = "middle"

  // 绘制背景
  ctx.fillStyle = "#e6ecfd"
  ctx.fillRect(0, 0, displayWidth, displayHeight)

  // 绘制文字
  for (let i = 0; i < value.value.length; i++) {
    drawText(ctx, value.value[i], i, displayWidth, displayHeight)
  }

  // 绘制干扰线
  drawLine(ctx, displayWidth, displayHeight)

  // 绘制干扰点
  drawDot(ctx, displayWidth, displayHeight)
}

/**
 * 绘制文本
 */
const drawText = (ctx: CanvasRenderingContext2D, txt: string, i: number, width: number, height: number) => {
  ctx.fillStyle = randomColor(50, 160) // 使用更深的颜色以提高可读性

  // 计算字体大小（考虑rem转换）
  let fontSize = randomNum(props.minFontSize, props.maxFontSize)
  if (props.isRem) {
    const htmlFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)
    fontSize = fontSize * htmlFontSize
  }

  ctx.font = `bold ${fontSize}px SimHei`

  const x = (i + 1) * (width / (value.value.length + 1))
  const y = height / 2
  const deg = randomNum(props.minDeg, props.maxDeg)

  // 保存当前状态
  ctx.save()

  // 变换坐标系统
  ctx.translate(x, y)
  ctx.rotate((deg * Math.PI) / 180)

  // 绘制文本
  ctx.fillText(txt, 0, 0)

  // 恢复状态
  ctx.restore()
}

/**
 * 绘制干扰线
 */
const drawLine = (ctx: CanvasRenderingContext2D, width: number, height: number) => {
  for (let i = 0; i < 4; i++) {
    ctx.strokeStyle = randomColor(props.minLineColor, props.maxLineColor)
    ctx.beginPath()
    ctx.moveTo(
        randomNum(0, width),
        randomNum(0, height)
    )
    ctx.lineTo(
        randomNum(0, width),
        randomNum(0, height)
    )
    ctx.stroke()
  }
}

/**
 * 绘制干扰点
 */
const drawDot = (ctx: CanvasRenderingContext2D, width: number, height: number) => {
  for (let i = 0; i < 30; i++) {
    ctx.fillStyle = randomColor(props.minDotColor, props.maxDotColor)
    ctx.beginPath()
    ctx.arc(
        randomNum(0, width),
        randomNum(0, height),
        1, // 点的大小
        0,
        2 * Math.PI
    )
    ctx.fill()
  }
}

/**
 * 获取当前验证码值
 */
const getValue = () => {
  return value.value
}

/**
 * 暴露方法给父组件
 */
defineExpose({
  getValue,
  handleRefresh
})

/**
 * 计算样式
 */
const style = computed(() => {
  if (props.isRem) {
    return {
      width: props.width + "rem",
      height: props.height + "rem"
    }
  } else {
    return {
      width: props.width + "px",
      height: props.height + "px"
    }
  }
})
</script>

<template>
  <div :style="style" class="verification-code" @click="handleRefresh">
    <canvas
        :id="guid"
        :style="style"
    ></canvas>
  </div>
</template>

<style lang="scss" scoped>
.verification-code {
  font-size: 0;
  cursor: pointer;
  display: inline-block;
  line-height: 1;

  canvas {
    vertical-align: top;
  }
}
</style>